﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Xunit;
using YAPL.Language.Ast;
using YAPL.Language.Ast.Utils;
using YAPL.Util;

namespace YAPL.Test.YAPL.Language.Ast.Utils {
	public class DefaultAstWalkerTest {

		[Fact]
		public void ConstructorArgumentShouldNotBeNull() {
			Assert.Throws<ArgumentNullException>(() => new DefaultAstWalker(null, AstWalkType.PreOrder));
		}

		[Fact]
		public void AstWithOneNodeShouldBeWalkedCorrectly() {
			var variable = Expressions.Variable("a");
			var walker = new DefaultAstWalker(variable, AstWalkType.PreOrder);

			ExpectNext(walker, variable);
			ExpectToFinish(walker);
		}

		[Fact]
		public void AssignmentExpressionShouldBeWalkedCorrectly() {
			var aVariable = Expressions.Variable("a");
			var literal = Expressions.StringLiteral("a value");
			var assignment = Expressions.Assignment(aVariable, literal);
			var walker = new DefaultAstWalker(assignment, AstWalkType.PreOrder);


			ExpectNext(walker, assignment);
			ExpectNext(walker,literal);
			ExpectNext(walker,aVariable);

			ExpectToFinish(walker);
		}

		[Fact]
		public void AssignmentOfBinaryExpressionShouldBeWalkedCorrectly() {
			var aVariable = Expressions.Variable("a");
			var bVariable = Expressions.Variable("b");
			var literal = Expressions.StringLiteral("string to add");
			var binaryExpr = Expressions.Binary(aVariable, literal, BinaryOperator.Plus);
			var assignment = Expressions.Assignment(bVariable, binaryExpr);
			var walker = new DefaultAstWalker(assignment, AstWalkType.PreOrder);


			ExpectNext(walker, assignment);
			ExpectNext(walker, binaryExpr);
			ExpectNext(walker,literal);
			ExpectNext(walker, aVariable);
			ExpectNext(walker, bVariable);

			ExpectToFinish(walker);
		}

		[Fact]
		public void AssignmentOfSetOfBinaryExpressionsShouldBeWalkedCorrectly() {
			var resultVariable = Expressions.Variable("result");
			var aVariable = Expressions.Variable("a");
			var bVariable = Expressions.Variable("b");
			var literal1 = Expressions.StringLiteral("literal1");
			var literal2 = Expressions.StringLiteral("literal2");
			var literal3 = Expressions.StringLiteral("literal3");

			var lit1PlusLit3 = Expressions.Binary(literal1, literal3, BinaryOperator.Plus);
			var aPlusLitSum = Expressions.Binary(aVariable, lit1PlusLit3, BinaryOperator.Plus);
			var bPlusLit2 = Expressions.Binary(bVariable, literal2, BinaryOperator.Plus);
			var finalSum = Expressions.Binary(aPlusLitSum, bPlusLit2, BinaryOperator.Plus);
			var assignment = Expressions.Assignment(resultVariable, finalSum);
			var walker = new DefaultAstWalker(assignment, AstWalkType.PreOrder);

			ExpectNext(walker, assignment);
			ExpectNext(walker, finalSum);
			ExpectNext(walker, bPlusLit2);
			ExpectNext(walker, literal2);
			ExpectNext(walker, bVariable);
			ExpectNext(walker, aPlusLitSum);
			ExpectNext(walker, lit1PlusLit3);

			ExpectNext(walker, literal3);
			ExpectNext(walker, literal1);
			ExpectNext(walker, aVariable);
			ExpectNext(walker, resultVariable);

			ExpectToFinish(walker);
		}

		[Fact]
		public void EmptyProgramShouldBeWalkedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.EmptyProgram_xml);
			var programDef = (ProgramDefExpression) expr;
			var body = programDef.ProgramBody;
			var walker = new DefaultAstWalker(expr, AstWalkType.PreOrder);

			ExpectNext(walker, programDef);
			ExpectNext(walker, body);
			ExpectToFinish(walker);
		}


		[Fact]
		public void HelloWorldProgramShouldBeWalkedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.HelloWorld_xml);
			var programDef = (ProgramDefExpression)expr;
			var funcCall = (FunctionCallExpression)programDef.ProgramBody.Expressions[0];
			var literal = funcCall.Arguments[0];
			var walker = new DefaultAstWalker(expr, AstWalkType.PreOrder);

			ExpectNext(walker, programDef);
			ExpectNext(walker, programDef.ProgramBody);
			ExpectNext(walker, funcCall);
			ExpectNext(walker, literal);
			ExpectToFinish(walker);
		}

		[Fact]
		public void AssignmentAndVariablesProgramShouldBeWalkedCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.AssignmentAndVariables_xml);
			var programDef = (ProgramDefExpression)expr;
			var bodyExprs = programDef.ProgramBody.Expressions;
			
			var assignment1 = (AssignmentExpression) bodyExprs[0];

			var assignment2 = (AssignmentExpression)bodyExprs[1];

			var assignment3 = (AssignmentExpression)bodyExprs[2];

			var funcCall1 = (FunctionCallExpression) bodyExprs[3];
			var funcCall2 = (FunctionCallExpression) bodyExprs[4];
			var funcCall3 = (FunctionCallExpression) bodyExprs[5];
			var walker = new DefaultAstWalker(expr, AstWalkType.PreOrder);

			ExpectNext(walker, programDef);
			ExpectNext(walker, programDef.ProgramBody);

			ExpectNext(walker, assignment1);
			ExpectNext(walker, assignment1.Right);
			ExpectNext(walker, assignment1.Left);

			ExpectNext(walker, assignment2);
			ExpectNext(walker, assignment2.Right);
			ExpectNext(walker, assignment2.Left);

			ExpectNext(walker, assignment3);
			ExpectNext(walker, assignment3.Right);
			ExpectNext(walker, assignment3.Left);

			ExpectNext(walker, funcCall1);
			ExpectNext(walker, funcCall1.Arguments[0]);
			ExpectNext(walker, funcCall2);
			ExpectNext(walker, funcCall2.Arguments[0]);
			ExpectNext(walker, funcCall3);
			ExpectNext(walker, funcCall3.Arguments[0]);
			ExpectToFinish(walker);
		}

		[Fact]
		public void PostOrderWalkShouldFinishInAstRoot() {
			var expr = TestHelper.GetAstFromYAPL(FixturePath.ConstantFormula_yapl);
			Expression currentExpr = null;
			var walker = new DefaultAstWalker(expr, AstWalkType.PostOrder);

			while (walker.MoveNext()) {
				currentExpr = walker.Current;
			}

			Assert.Same(expr, currentExpr);
		}

		[Fact]
		public void HelloWorldProgramPostOrderWalkShouldWorkCorrectly() {
			var expr = TestHelper.GetAstFromXML(FixturePath.HelloWorld_xml);
			var programDef = (ProgramDefExpression)expr;
			var funcCall = (FunctionCallExpression)programDef.ProgramBody.Expressions[0];
			var literal = funcCall.Arguments[0];
			var walker = new DefaultAstWalker(expr, AstWalkType.PostOrder);

			ExpectNext(walker, literal);
			ExpectNext(walker, funcCall);
			ExpectNext(walker, programDef.ProgramBody);
			ExpectNext(walker, programDef);
			ExpectToFinish(walker);
		}

		[Fact]
		public void AstShouldBeWalkedCorrectlyPWhenNodesAreRewritten() {
			var expr1 = Expressions.Assignment(Expressions.Variable("a"), Expressions.Integer(1));
			var expr2 = Expressions.Unary(Expressions.Variable("a"), UnaryOperator.Decrement);
			var codeBlock = Expressions.CodeBlock(new List<Expression> { expr1, expr2 });
			var walker = new DefaultAstWalker(codeBlock, AstWalkType.PreOrder);

			var intExp = Expressions.Integer(1);
			var replacement = Expressions.ReturnStatement(intExp);

			ExpectNext(walker,codeBlock);
			ExpectNext(walker, expr1);
			walker.RewriteCurrent(replacement);
			ExpectNext(walker, intExp);
			ExpectNext(walker, expr2);

		}


		private static void ExpectNext(IAstWalker walker, Expression expr) {
			Assert.True(walker.MoveNext());
			Assert.IsType(expr.GetType(), walker.Current);
			Assert.True(expr.NodeEquals(walker.Current));
		}

		private static void ExpectToFinish(IAstWalker walker) {
			Assert.False(walker.MoveNext());
			Assert.Same(Expressions.Undefined(), walker.Current);
		}
	}
}
